<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="nHg9p" id="nHg9p"><span data-lake-id="u304bfc33" id="u304bfc33">典型回答</span></h1>
  <p data-lake-id="ue02002c9" id="ue02002c9"><br></p>
  <p data-lake-id="u4a757a51" id="u4a757a51"><span data-lake-id="uf2d7d852" id="uf2d7d852">Redis的数据分片（sharding）是一种将一个Redis数据集分割成多个部分，分别存储在不同的Redis节点上的技术。它可以用于将一个单独的Redis数据库扩展到多个物理机器上，从而提高Redis集群的性能和可扩展性。</span></p>
  <p data-lake-id="u2799d24d" id="u2799d24d"><span data-lake-id="uc8854e66" id="uc8854e66">​</span><br></p>
  <p data-lake-id="u9d633d6d" id="u9d633d6d"><span data-lake-id="uabda4d8f" id="uabda4d8f">Redis数据分片的实现方式通常是将数据按照某种规则（例如，key的hash值）分配到不同的节点上。当客户端想要访问某个key时，它会先计算出这个key应该存储在哪个节点上，然后直接连接到该节点进行操作。因此，对于客户端而言，Redis集群就像是一个大型的、统一的数据库，而不需要关心数据的实际分布情况。</span></p>
  <p data-lake-id="ubccfe022" id="ubccfe022"><span data-lake-id="u6017d415" id="u6017d415">​</span><br></p>
  <p data-lake-id="ua0e320a8" id="ua0e320a8"><strong><span data-lake-id="u2aa264a1" id="u2aa264a1">在Redis的Cluster 集群模式中，使用哈希槽（hash slot）的方式来进行数据分片</span></strong><span data-lake-id="ua9e74ae6" id="ua9e74ae6">，将整个数据集划分为多个槽，每个槽分配给一个节点。客户端访问数据时，先计算出数据对应的槽，然后直接连接到该槽所在的节点进行操作。Redis Cluster还提供了自动故障转移、数据迁移和扩缩容等功能，能够比较方便地管理一个大规模的Redis集群。</span></p>
  <p data-lake-id="u21ae70f9" id="u21ae70f9"><span data-lake-id="uc22f74ca" id="uc22f74ca">​</span><br></p>
  <p data-lake-id="u710d4608" id="u710d4608"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1702293948603-17c6942a-6a69-493d-b233-a9c9e39ff8e6.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_33%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="uf3633dfe" id="uf3633dfe"><br></p>
  <p data-lake-id="u15048382" id="u15048382"><span data-lake-id="udf18fe72" id="udf18fe72">Redis Cluster将整个数据集划分为</span><strong><span data-lake-id="uf431738e" id="uf431738e">16384个槽</span></strong><span data-lake-id="u3fe790ef" id="u3fe790ef">，每个槽都有一个编号（0~16383），集群的每个节点可以负责多个hash槽，客户端访问数据时，先根据key计算出对应的槽编号，然后根据槽编号找到负责该槽的节点，向该节点发送请求。</span></p>
  <p data-lake-id="u8dacf044" id="u8dacf044"><span data-lake-id="u9150e072" id="u9150e072">​</span><br></p>
  <p data-lake-id="uca028d44" id="uca028d44"><span data-lake-id="ue4237e68" id="ue4237e68">在 Redis 的每一个节点上，都有这么两个东西，一个是槽（slot），它的的取值范围是：0-16383。还有一个就是 cluster，可以理解为是一个集群管理的插件。当我们的存取的 Key 的时候，Redis 会根据 </span><strong><span data-lake-id="ueed2e25f" id="ueed2e25f">CRC16 算法</span></strong><span data-lake-id="u77ba2db0" id="u77ba2db0">得出一个结果，然后把结果对 16384 求余数，这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽，通过这个值，去找到对应的插槽所对应的节点，然后直接自动跳转到这个对应的节点上进行存取操作。</span></p>
  <p data-lake-id="u1e0326ce" id="u1e0326ce"><span data-lake-id="ua55d4a18" id="ua55d4a18">​</span><br></p>
  <p data-lake-id="u1f62d907" id="u1f62d907"><span data-lake-id="u73791759" id="u73791759">Redis Cluster中的数据分片具有以下特点：</span></p>
  <ol list="u8d0fd7c5">
   <li fid="u17298173" data-lake-id="u2a60b40f" id="u2a60b40f"><strong><span data-lake-id="u0ca6af3f" id="u0ca6af3f">提升性能和吞吐量</span></strong><span data-lake-id="ud06a10c5" id="ud06a10c5">：通过在多个节点上分散数据，可以并行处理更多的操作，从而提升整体的性能和吞吐量。这在高流量场景下尤其重要，因为单个节点可能无法处理所有请求。</span></li>
   <li fid="u17298173" data-lake-id="uab8e74c9" id="uab8e74c9"><strong><span data-lake-id="u435c4a19" id="u435c4a19">提高可扩展性</span></strong><span data-lake-id="u08bc8f8d" id="u08bc8f8d">：分片使得Redis可以水平扩展。可以通过添加更多节点扩展数据库的容量和处理能力。</span></li>
   <li fid="u17298173" data-lake-id="u1f3964c9" id="u1f3964c9"><strong><span data-lake-id="ud27821b0" id="ud27821b0">更好的资源利用</span></strong><span data-lake-id="u8bdb1d7c" id="u8bdb1d7c">：分片允许更有效地利用服务器资源。每个节点只处理数据的一部分，这降低了单个节点的内存和计算需求。</span></li>
   <li fid="u17298173" data-lake-id="u2c197db3" id="u2c197db3"><strong><span data-lake-id="u899eb6f5" id="u899eb6f5">避免单点故障</span></strong><span data-lake-id="uc6faff8a" id="uc6faff8a">：在没有分片的情况下，如果唯一的Redis服务器发生故障，整个服务可能会停止。在分片的环境中，即使一个节点出现问题，其他节点仍然可以继续运行。</span></li>
   <li fid="u17298173" data-lake-id="u5238a8d0" id="u5238a8d0"><strong><span data-lake-id="uf6ab57fd" id="uf6ab57fd">数据冗余和高可用性</span></strong><span data-lake-id="u1ec3b505" id="u1ec3b505">：在某些分片策略中，如Redis集群，每个分片的数据都可以在集群内的其他节点上进行复制。这意味着即使一个节点失败，数据也不会丢失，从而提高了系统的可用性。</span></li>
  </ol>
  <p data-lake-id="uba6c63c2" id="uba6c63c2"><span data-lake-id="udc969120" id="udc969120">​</span><br></p>
  <h1 data-lake-id="A03Ar" id="A03Ar"><span data-lake-id="u6cc22d47" id="u6cc22d47">扩展知识</span></h1>
  <p data-lake-id="ub4ee0fd9" id="ub4ee0fd9"><br></p>
  <h2 data-lake-id="lAHOc" id="lAHOc"><span data-lake-id="u769b9c9e" id="u769b9c9e">16384</span></h2>
  <p data-lake-id="u33d15a57" id="u33d15a57"><br></p>
  <p data-lake-id="uafa0fb14" id="uafa0fb14"><strong><span data-lake-id="ua853a266" id="ua853a266">Redis Cluster将整个数据集划分为16384个槽，为什么是16384呢，这个数字有什么特别的呢？</span></strong></p>
  <p data-lake-id="u4702910f" id="u4702910f"><br></p>
  <p data-lake-id="u478d2bea" id="u478d2bea"><span data-lake-id="ub3677c67" id="ub3677c67">这个问题在Github上有所讨论，Redis的作者也下场做过回复：</span><a href="https://github.com/redis/redis/issues/2576" target="_blank" data-lake-id="u45c7b0a2" id="u45c7b0a2"><span data-lake-id="uc07a0df2" id="uc07a0df2">https://github.com/redis/redis/issues/2576</span></a></p>
  <p data-lake-id="u577f059b" id="u577f059b"><span data-lake-id="u8c75e97c" id="u8c75e97c">​</span><br></p>
  <blockquote data-lake-id="u1c5de5ba" id="u1c5de5ba">
   <p data-lake-id="u12119527" id="u12119527"><span data-lake-id="u60cb6c04" id="u60cb6c04" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">The reason is:</span></p>
   <p data-lake-id="ud557184d" id="ud557184d"><span data-lake-id="uae90c289" id="uae90c289" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">​</span><br></p>
   <p data-lake-id="ub363e4b6" id="ub363e4b6"><span data-lake-id="ue61efdcb" id="ue61efdcb" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">1、Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots.</span></p>
   <p data-lake-id="uaeb3db5a" id="uaeb3db5a"><span data-lake-id="u40266c85" id="u40266c85" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">​</span><br></p>
   <p data-lake-id="uff9dbe91" id="uff9dbe91"><span data-lake-id="u11105cd2" id="u11105cd2" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">2、At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.</span></p>
   <p data-lake-id="uf86093dc" id="uf86093dc"><span data-lake-id="u8bbf8e8f" id="u8bbf8e8f" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">​</span><br></p>
   <p data-lake-id="ua1d20078" id="ua1d20078"><span data-lake-id="u9007be14" id="u9007be14" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.</span></p>
  </blockquote>
  <p data-lake-id="u4e241915" id="u4e241915"><br></p>
  <p data-lake-id="u374108d2" id="u374108d2"><span data-lake-id="u323f0951" id="u323f0951" class="lake-fontsize-11">16384这个数字是一个2的14次方（2^14），尽管</span><span data-lake-id="u01d71891" id="u01d71891" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">crc16能得到2^16 -1=65535个值，但是并没有选择，</span><span data-lake-id="u20c59f50" id="u20c59f50" class="lake-fontsize-11">主要从消息大小和集群规模等方面考虑的：</span></p>
  <p data-lake-id="u995cccb7" id="u995cccb7"><span data-lake-id="u8b12a3d7" id="u8b12a3d7" class="lake-fontsize-11">​</span><br></p>
  <p data-lake-id="u4ef422ab" id="u4ef422ab"><span data-lake-id="u9e898494" id="u9e898494">1、正常的心跳数据包携带了节点的完整配置，在更新配置的时候，可以以幂等方式进行替换。这意味着它们包含了节点的原始槽配置，对于包含</span><span data-lake-id="u6c387f43" id="u6c387f43" class="lake-fontsize-11">16384</span><span data-lake-id="ua90e334c" id="ua90e334c">个槽位的情况，使用2k的空间就够了，但如果使用</span><span data-lake-id="ud44dd89f" id="ud44dd89f" class="lake-fontsize-11" style="color: rgb(31, 35, 40)">65535</span><span data-lake-id="u5cfe9583" id="u5cfe9583">个槽位，则需要使用8k的空间，这就有点浪费了。</span></p>
  <p data-lake-id="u5f2a617e" id="u5f2a617e"><span data-lake-id="u73ee632a" id="u73ee632a">​</span><br></p>
  <p data-lake-id="u1a24e100" id="u1a24e100"><span data-lake-id="u968f0498" id="u968f0498">2、由于其他设计权衡的原因，Redis Cluster不太可能扩展到超过1000个主节点，这种情况下，用65535的话会让每个节点上面的slot太多了，会导致节点的负载重并且数据迁移成本也比较高。而16384是相对比较好的选择，可以在1000个节点下使得slot均匀分布，每个分片平均分到的slot不至于太小。</span></p>
  <p data-lake-id="u7742d7de" id="u7742d7de"><br></p>
  <p data-lake-id="u7df7d063" id="u7df7d063"><span data-lake-id="u6c70ce51" id="u6c70ce51">除此之外，还有一些原因和优点供大家参考：</span></p>
  <p data-lake-id="ub4384beb" id="ub4384beb"><span data-lake-id="u376462af" id="u376462af">​</span><br></p>
  <ol list="u6a8a9ee8">
   <li fid="u3c4eec6c" data-lake-id="ud24d724c" id="ud24d724c"><span data-lake-id="ufb910f27" id="ufb910f27">易于扩展：槽数量是一个固定的常数，这样就可以方便地进行集群的扩展和缩小。如果需要添加或删除节点，只需要将槽重新分配即可。</span></li>
   <li fid="u3c4eec6c" data-lake-id="ub6869773" id="ub6869773"><span data-lake-id="u6c7f6239" id="u6c7f6239">易于计算：哈希算法通常是基于槽编号计算的，</span><strong><span data-lake-id="ue18113ae" id="ue18113ae">将槽数量设置为2的幂次方，可以使用位运算等简单的算法来计算槽编号，从而提高计算效率。</span></strong></li>
   <li fid="u3c4eec6c" data-lake-id="uff2df7f8" id="uff2df7f8"><span data-lake-id="u75bceee6" id="u75bceee6">负载均衡：槽数量的选择可以影响数据的负载均衡。如果槽数量太少，会导致某些节点负载过重；如果槽数量太多，会导致数据迁移的开销过大。</span><strong><span data-lake-id="ua20d1012" id="ua20d1012">16384这个数量在实践中被证明是一个比较合适的选择，能够在保证负载均衡的同时，减少数据迁移的开销。</span></strong></li>
  </ol>
  <p data-lake-id="u09778382" id="u09778382"><br></p>
  <p data-lake-id="ud98b25a5" id="ud98b25a5"><br></p>
  <p data-lake-id="u0ca214a9" id="u0ca214a9"><br></p>
  <h2 data-lake-id="FDXmJ" id="FDXmJ"><span data-lake-id="u220215bc" id="u220215bc">CRC16算法</span></h2>
  <p data-lake-id="u9d8c7729" id="u9d8c7729"><u><span data-lake-id="u9185b5f6" id="u9185b5f6">（简单了解即可，面试一般不做要求）</span></u></p>
  <p data-lake-id="ufc8a158c" id="ufc8a158c"><span data-lake-id="ubc55bc8f" id="ubc55bc8f">​</span><br></p>
  <p data-lake-id="u6de6d94a" id="u6de6d94a"><span data-lake-id="u89e39746" id="u89e39746">当我们的存取的 Key 的时候，Redis 会根据 </span><strong><span data-lake-id="u0b51c905" id="u0b51c905">CRC16 算法</span></strong><span data-lake-id="u8b5ff55c" id="u8b5ff55c">得出一个结果，然后把结果对 16384 求余数，这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽。</span></p>
  <p data-lake-id="u7ba77086" id="u7ba77086"><span data-lake-id="u9cf9ad23" id="u9cf9ad23">​</span><br></p>
  <p data-lake-id="u1a385f70" id="u1a385f70"><span data-lake-id="ud8f83dc1" id="ud8f83dc1">那么，什么是CRC16算法呢？</span></p>
  <p data-lake-id="ua1a43ba3" id="ua1a43ba3"><span data-lake-id="u5935c44a" id="u5935c44a">​</span><br></p>
  <p data-lake-id="u72d0c93c" id="u72d0c93c"><span data-lake-id="u38e1627a" id="u38e1627a">CRC16（Cyclic Redundancy Check，循环冗余校验码）算法是一种广泛使用的校验算法，主要用于数据通信和数据存储等领域，例如网络通信中的错误检测和校正、数据存储中的文件校验和等。</span></p>
  <p data-lake-id="uf8f1f333" id="uf8f1f333"><span data-lake-id="u955d581f" id="u955d581f">​</span><br></p>
  <p data-lake-id="u9032eafb" id="u9032eafb"><span data-lake-id="uf7aa4e51" id="uf7aa4e51">CRC16算法基于多项式除法，将输入数据按位进行多项式除法运算，最后得到一个16位的校验码。CRC16算法的计算过程包括以下几个步骤：</span></p>
  <p data-lake-id="u493211e9" id="u493211e9"><span data-lake-id="ueef56555" id="ueef56555">​</span><br></p>
  <ol list="uaa95b05e">
   <li fid="u4e51cc47" data-lake-id="u98ec36b2" id="u98ec36b2"><span data-lake-id="u04ab32b1" id="u04ab32b1">初始化一个16位的寄存器为全1；</span></li>
   <li fid="u4e51cc47" data-lake-id="u5c3cb376" id="u5c3cb376"><span data-lake-id="uc41500bb" id="uc41500bb">将输入数据的第一个字节与16位寄存器的低8位进行异或操作，结果作为新的16位寄存器的值；</span></li>
   <li fid="u4e51cc47" data-lake-id="ubff2aa50" id="ubff2aa50"><span data-lake-id="u8b2fb1c4" id="u8b2fb1c4">将16位寄存器的高8位和低8位分别右移一位，丢弃掉最低位，即寄存器右移一位；</span></li>
   <li fid="u4e51cc47" data-lake-id="u8c2e71aa" id="u8c2e71aa"><span data-lake-id="u0651477e" id="u0651477e">如果输入数据还没有处理完，转到第2步继续处理下一个字节；</span></li>
   <li fid="u4e51cc47" data-lake-id="u72d081ca" id="u72d081ca"><span data-lake-id="ue28e6841" id="ue28e6841">如果输入数据已经处理完，将16位寄存器的值取反，得到CRC16校验码。</span></li>
  </ol>
  <p data-lake-id="u91b6a59a" id="u91b6a59a"><br></p>
  <p data-lake-id="u31bc9e45" id="u31bc9e45"><span data-lake-id="u07628f6b" id="u07628f6b">CRC16算法的多项式是一个固定的16位二进制数，不同的CRC16算法使用的多项式也不相同。例如，CRC-16/CCITT算法使用的多项式为0x1021，而Modbus CRC16算法使用的多项式为0xA001。</span></p>
  <p data-lake-id="ua907a287" id="ua907a287"><span data-lake-id="ubaf25e89" id="ubaf25e89">​</span><br></p>
  <p data-lake-id="u38a5d169" id="u38a5d169"><span data-lake-id="ua1279eff" id="ua1279eff">CRC16算法的优点是计算速度快，校验效果好，具有广泛的应用范围。缺点是只能检测错误，无法纠正错误。如果数据被修改，CRC校验值也会被修改，但无法确定是哪一位数据被修改。因此，在数据传输和存储中，通常需要与其它校验算法配合使用，以保证数据的完整性和正确性。</span></p>
  <p data-lake-id="uff5798fb" id="uff5798fb"><span data-lake-id="udb08af2e" id="udb08af2e">​</span><br></p>
  <p data-lake-id="ub7a2fbd1" id="ub7a2fbd1"><span data-lake-id="u8e776c84" id="u8e776c84">​</span><br></p>
  <p data-lake-id="u50bbeb8f" id="u50bbeb8f"><span data-lake-id="u2dbadf44" id="u2dbadf44">​</span><br></p>
 </body>
</html>